Tutorial 5 - Advanced quantum programming
Note: This tutorial is designed for users who already understand quantum programming using the pulser library. For more details on this library, see its tutorial (external).
By design, a quantum solver is composed of two main quantum components for solving a problem:
- an embedder, i.e. a mechanism used to customize the layout of neutral atoms on the quantum device.
- a pulse shaper, i.e. a mechanism used to customize the laser pulse to which the neutral atoms are subjected during the execution of the quantum algorithm.
These can be specified in the SolverConfig. Most users can use the default embedders and pulse shapers provided with this library. However, if you wish to alter the behavior of the solver, perhaps for the sake of research, debugging or learning, you can implement your own embedders or pulse shapers. This tutorial dives into currently available components, as well as designing custom ones.
Embedder
Section titled “Embedder”Default
Section titled “Default”When instantiating a SolverConfig, a default embedder is already made available. To access the resulting embedding from a solver, simply call the embedding method as follows:
from mis import MISSolver, MISInstance, SolverConfig, BackendConfig, BackendType
from networkx import erdos_renyi_graph
# User can fix the seed for reproducibilityseed = 0graph = erdos_renyi_graph(n=25, p=0.4, seed=seed)instance = MISInstance(graph)
qutip_config = BackendConfig( backend = BackendType.QUTIP)
config = SolverConfig(backend = qutip_config)solver = MISSolver(instance, config)
geometry = solver.embedding()
# draw the register# geometry.draw()Custom
Section titled “Custom”To design our own embedding method, we need to define a class inhereting from mis.pipeline.embedder.BaseEmbedder and implement an embed method as follows:
from mis.pipeline.embedder import BaseEmbedderfrom pulser import Registerfrom mis._backends.backends import BaseBackendfrom mis.pipeline.layout import Layout
class DefaultEmbedder(BaseEmbedder): """ The DefaultEmbedder class available in mis. """
def embed(self, instance: MISInstance, config: SolverConfig, backend: BaseBackend) -> Register: device = backend.device()
# Use Layout helper to get rescaled coordinates and interaction graph layout = Layout.from_device(data=instance, device=device)
# Finally, prepare register. return Register( qubits={f"q{node}": pos for (node, pos) in layout.coords.items()} )
custom_config = SolverConfig(backend = qutip_config, embedder=DefaultEmbedder())custom_solver = MISSolver(instance, custom_config)default_geometry = custom_solver.embedding()
# draw the register# default_geometry.draw()Pulse shaper
Section titled “Pulse shaper”Default
Section titled “Default”When instantiating a SolverConfig, a default pulse shaper is already made available. To access the resulting embedding from a solver, simply call the pulse method as follows:
default_pulse = solver.pulse(solver.embedding())default_pulsePulse(amp=InterpolatedWaveform(Points: (0, 1e-09), (3000, 15.71), (5999, 1e-09), Interpolator=PchipInterpolator) rad/µs, detuning=InterpolatedWaveform(Points: (0, -125.7), (3000, 0), (5999, 125.7), Interpolator=PchipInterpolator) rad/µs, phase=0, post_phase_shift=0)
Custom
Section titled “Custom”To design our own pulse shaping method, we need to define a class inhereting from mis.pipeline.pulse.BasePulseShaper and implement a pulse method and a detuning method as follows:
from mis.pipeline.pulse import BasePulseShaperfrom pulser import Pulsefrom mis._backends import Detuning
class ConstantPulseShaper(BasePulseShaper): """ We simply return a constant pulse. """
def pulse( self, config: SolverConfig, register: Register, backend: BaseBackend, instance: MISInstance ) -> Pulse: import numpy as np return Pulse.ConstantPulse(1000, np.pi, 0, 0)
def detuning( self, config: SolverConfig, register: Register, backend: BaseBackend, instance: MISInstance ) -> list[Detuning]: return list()constant_pulse_config = SolverConfig(backend = qutip_config, pulse_shaper=ConstantPulseShaper())constant_pulse_solver = MISSolver(instance, constant_pulse_config)constant_pulse = constant_pulse_solver.pulse(constant_pulse_solver.embedding())constant_pulsePulse(amp=ConstantWaveform(1000 ns, 3.14) rad/µs, detuning=ConstantWaveform(1000 ns, 0) rad/µs, phase=0, post_phase_shift=0)